// midisongloader.cpp

// 2008-05-01 17:07:36 (Jake): created

#include "crankit_precompiled.h"

#include <stdexcept>

#include "game.h"
#include "midisongloader.h"

using namespace std;

MidiSongLoader::MidiNoteMap MidiSongLoader::_midiNoteMap;

MidiSongLoader::MidiSongLoader()
{

}

void MidiSongLoader::load(const std::string& file)
{
	clear();

	MidiParser parser;
	parser.readHeaderAndTracks(file, *this);
}

void MidiSongLoader::clear()
{
	_notesOn.clear();
	_notes.clear();
	_tempoList.clear();
}

void MidiSongLoader::initMidiNoteMap()
{
	_midiNoteMap[0x60] = MidiNote(Level::Amazing, 0);
	_midiNoteMap[0x61] = MidiNote(Level::Amazing, 1);
	_midiNoteMap[0x62] = MidiNote(Level::Amazing, 2);
	_midiNoteMap[0x63] = MidiNote(Level::Amazing, 3);
	_midiNoteMap[0x64] = MidiNote(Level::Amazing, 4);
	_midiNoteMap[0x54] = MidiNote(Level::Medium, 0);
	_midiNoteMap[0x55] = MidiNote(Level::Medium, 1);
	_midiNoteMap[0x56] = MidiNote(Level::Medium, 2);
	_midiNoteMap[0x57] = MidiNote(Level::Medium, 3);
	_midiNoteMap[0x58] = MidiNote(Level::Medium, 4);
	_midiNoteMap[0x48] = MidiNote(Level::Easy, 0);
	_midiNoteMap[0x49] = MidiNote(Level::Easy, 1);
	_midiNoteMap[0x4a] = MidiNote(Level::Easy, 2);
	_midiNoteMap[0x4b] = MidiNote(Level::Easy, 3);
	_midiNoteMap[0x4c] = MidiNote(Level::Easy, 4);
	_midiNoteMap[0x3c] = MidiNote(Level::SupaEasy, 0);
	_midiNoteMap[0x3d] = MidiNote(Level::SupaEasy, 1);
	_midiNoteMap[0x3e] = MidiNote(Level::SupaEasy, 2);
	_midiNoteMap[0x3f] = MidiNote(Level::SupaEasy, 3);
	_midiNoteMap[0x40] = MidiNote(Level::SupaEasy, 4);
}

const MidiNote& MidiSongLoader::getMidiNote(int note) 
{
	if (_midiNoteMap.empty()) {
		initMidiNoteMap();
	}

	MidiNoteMap::const_iterator iter = _midiNoteMap.find(note);
	if (iter == _midiNoteMap.end()) {
		throw runtime_error("unknown note found in midi file");
	}	

	return iter->second;
}

void MidiSongLoader::header(int type, int numTracks, unsigned char div_b1, unsigned char div_b0)
{
	_subframe = _fps = _division = 0;

	if ((div_b1 & 0x80) != 0) {
		_fps = (char)div_b1;
		_subframe = div_b0;
		// TODO
		throw runtime_error("dont know how to handle midi files with fps and subframe time info yet..");
	}
	else {
		_division = (div_b1 << 8) | div_b0;
		_ticksPerBeat = _division;
	}

	_type = type;
	_numTracks = numTracks;
}

void MidiSongLoader::beginTrack(int track, int size)
{
	_time = 0;
	_currentTime = 0;
	_track = track;
}

void MidiSongLoader::endTrack()
{
	for (std::map< MidiNote, SongNote >::iterator iter = _notesOn.begin();
		iter != _notesOn.end();
		++iter)
	{
		SongNote& songNote(iter->second);
//		songNote.length = _time - songNote.time;
		songNote.length = _currentTime - songNote.time;
		addNote(iter->second.level, songNote);
	}

	_notesOn.clear();
}

void MidiSongLoader::relTime(int t)
{
	if (t > 0) {
		_currentTime += midiTimeToTime(_time, _time + t);
		_time += t;
	}
}

void MidiSongLoader::eventNoteOn(int channel, int note, int velocity)
{
	const MidiNote& midiNote = getMidiNote(note);

	SongNote songNote;
	songNote.band = midiNote.band;
//	songNote.time = _time;
	songNote.time = _currentTime;
	songNote.type = 0;
	songNote.level = midiNote.level;

	_notesOn.insert(std::pair<MidiNote, SongNote>(midiNote, songNote));
}

void MidiSongLoader::eventNoteOff(int channel, int note, int velocity)
{
	const MidiNote& midiNote = getMidiNote(note);
	map<MidiNote, SongNote>::iterator iter = _notesOn.find(midiNote);
	if (iter != _notesOn.end()) {
		SongNote& songNote = iter->second;
//		songNote.length = _time - songNote.time;
		songNote.length = _currentTime - songNote.time;
		addNote(iter->first.level, songNote); 
		_notesOn.erase(iter);
	}
	else {
		throw runtime_error("bad midi file, got eventNoteOff before eventNoteOn");
	}
}

void MidiSongLoader::tempo(int tempo)
{
	_tempoList.push_back(SongTempo(_time, _currentTime, tempo));
//	_currentTempo = tempo;
//	_currentPeriod = 60000.0f / tempo;
}

float MidiSongLoader::midiTimeToTime(int midiTimeStart, int midiTimeEnd)
{
	if (midiTimeStart == midiTimeEnd) {
		return 0;
	}

	if (_tempoList.empty()) {
		throw runtime_error("midiTimeToTime called with empty tempo list");
	}

	// TODO: we do not support change of tempo in the song right now..
	int midiTime = midiTimeEnd - midiTimeStart;
	int tempo = _tempoList[0].tempo;
	float bpm = 60 * 1.0e6 / tempo;
	float beats = midiTime * 60000.0f / (bpm * _ticksPerBeat);

	return beats;
}

void MidiSongLoader::addNote(int level, const SongNote& songNote)
{
	/*
	map<int, SongNotes>::iterator iter = _notes.find(level);
	if (iter == _notes.end()) {
		_notes.insert(pair<int, SongNotes>(level, SongNotes()));
		iter = _notes.find(level);
	}

	SongNotes& songNotes = iter->second;
	*/
	_notes.push_back(songNote);
}

